/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.maze.individual;

import java.util.ArrayList;
import java.util.Random;
import org.maze.Maze;
import org.maze.behaviours.ICrossBehaviour;
import org.maze.behaviours.IFitnessBehaviour;
import org.maze.behaviours.IImprovementBehaviour;
import org.maze.behaviours.IMutateBehaviour;
import org.maze.configuration.IndividualConfiguration;
import org.maze.utils.Vector;

/**
 *
 * @author Thomas
 */
public class Individual implements Cloneable{
    public static final byte NORTH=0;
    public static final byte SOUTH=1;
    public static final byte EAST=2;
    public static final byte WEST=3;
    private static final Random rand = new Random();

    private byte[] genome;
    private IndividualConfiguration configuration;
    private ArrayList<Integer> path;
    private int size;

    public Individual(byte[] genome, IndividualConfiguration configuration){
        size = Maze.SIZE*Maze.SIZE;
        this.genome=genome;
        this.configuration=configuration;
        computePath();
    }

    @Override
    public Object clone() throws CloneNotSupportedException{
        return new Individual(genome, configuration);
    }

    public byte[] getGenome(){
        return genome.clone();
    }

    public Individual[] cross(Individual mother){
        ICrossBehaviour crossBehaviour = configuration.getCrossBehaviour();
        byte[][] childrenGenome = crossBehaviour.cross(this, mother);
        Individual[] children = new Individual[2];
        children[0] = new Individual(childrenGenome[0], configuration);
        children[1] = new Individual(childrenGenome[1], configuration);
        return children;
    }

    public void mutate() throws Exception{
        ArrayList<IMutateBehaviour> mutateBehaviour = configuration.getMutateBehaviours();
        for(IMutateBehaviour behaviour : mutateBehaviour){
            genome = behaviour.mutate(genome);
            this.computePath();
        }
    }

    public double getFitnessScore(){
        IFitnessBehaviour fitnessBehaviour = configuration.getFitnessBehaviour();
        this.computePath();
        return fitnessBehaviour.computeFitness(this);
    }

    public void improve() throws Exception{
        ArrayList<IImprovementBehaviour> improvementBehaviours = configuration.getImprovementBehaviours();
        for(IImprovementBehaviour behaviour : improvementBehaviours){            
            this.computePath();
            genome = behaviour.improve(this);
        }
    }

    private void computePath(){
        PathCalculator calcul = new PathCalculator();
        calcul.compute(0);
    }

    public ArrayList<Integer> getPath(){
        return path;
    }

    public double getDiversity(Individual mother) {
        int k=0;
        byte[] g2 = mother.getGenome();
        for(int i = 0; i<size; i++){
            if(genome[i]!= g2[i])
                k++;
        }
        return ((double)k)/((double)size);
    }

    public IndividualConfiguration getConfiguration() {
        return configuration;
    }

    private class PathCalculator{
        private int currentPosition;
        byte[] grid;

        public PathCalculator(){
            path = new ArrayList<Integer>();
            grid = Maze.getInstance().getGrid();
        }

        private void compute(int iteration){
            if(iteration >= size)
                return;
            int newPosition=0;
            byte gene = genome[iteration++];
            if(gene==EAST){
                // out of bundary
                if( ( currentPosition%Maze.SIZE )+1 == Maze.SIZE)
                    return;
                newPosition=currentPosition+1;
            }else if (gene == WEST){
                // out of bundary
                if( ( currentPosition%Maze.SIZE ) == 0)
                    return;
                newPosition=currentPosition-1;
            }else if (gene == NORTH){
                newPosition=currentPosition-Maze.SIZE;
            }else if (gene == SOUTH){
                newPosition=currentPosition+Maze.SIZE;
            }

            //test if out of bundary
            if((newPosition < 0) || (newPosition >= size ))
                return;

            

            // test if walk in a wall
            if(grid[newPosition]==Maze.WALL_FIELD)
                return;
            if(grid[newPosition]==-1)
                return;

            currentPosition=newPosition;
            if(currentPosition!=0)
                path.add(currentPosition); // add the point to the list
            else
                return;

            /*System.out.print("Command : ");
            System.out.println(gene);*/
            int x = currentPosition%Maze.SIZE;
            int y = currentPosition/Maze.SIZE;
            if(grid[newPosition]==Maze.MAZE_EXIT)
                return;
            grid[currentPosition]=-1;
            // we continue
            compute(iteration);
        }
    }


    public static byte[] fillBlank(byte[] genome, int start) throws Exception{
        assert start < Maze.SIZE*Maze.SIZE-1;
        return Individual.fillBlank(genome, start, genome.length);
    }

    public static byte[] fillBlank(byte[] genome, int start, int end) throws Exception{
        if(start<0 || start >= end)
            throw new Exception("Start error : "+start);
        if(end > genome.length)
            throw new Exception("End error : "+end);

        Vector vector = new Vector();
        //System.out.println(start);
        genome[start]=Individual.getRandomDirection();

        byte gval = genome[start];
        if(gval == NORTH)
            vector.y-=1;
        else if(gval == SOUTH)
            vector.y+=1;
        else if(gval == WEST)
            vector.x-=1;
        else if(gval == EAST)
            vector.x+=1;
        for(int i=start+1; i<end; i++){
            int x,y;
            do{
                gval=Individual.getRandomDirection();
                x=0;
                y=0;
                if(gval == NORTH)
                    y-=1;
                else if(gval == SOUTH)
                    y+=1;
                else if(gval == WEST)
                    x-=1;
                else if(gval == EAST)
                    x+=1;
            }while((vector.x+x == 0) && (vector.y+y==0));
            vector.x+=x;
            vector.y+=y;
            genome[i]=gval;
        }
        return genome;
       }

        protected static byte getRandomDirection(){
            
            int v = rand.nextInt(4);
            if(v==0)
                return NORTH;
            else if(v==1)
                return SOUTH;
            else if(v==2)
                return WEST;
            else if(v==3)
                return EAST;
            return NORTH;
        }
}
